﻿#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdarg.h>
#include <unistd.h>
#include <sys/time.h>
#include <time.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <arpa/inet.h>
#include "ngx_c_conf.h"
#include "ngx_macro.h"
#include "ngx_global.h"
#include "ngx_func.h"
#include "ngx_c_socket.h"
/***************************************************************
 * @file       ngx_c_socket_accept.cxx
 * @brief      建立新连接专用函数，当新连接进入时，本函数会被
 *             ngx_epoll_process_events()所调用
 * @author     txj
 * @version    v1
 * @return     
 * @date       2021/7/29
 **************************************************************/
void CSocekt::ngx_event_accept(lpngx_connection_t oldc)
{
    struct sockaddr    mysockaddr;
    socklen_t          socklen;
    int  err，level， s;
    static int    use_accept4 = 1;
    lpngx_connection_t newc;
    socklen = sizeof(mysockaddr);
    do
    {
        /*用accept4函数可以直接设置套接字为非阻塞套接字，
        但不一定每个系统都有这个函数，所以这里先默认有，如果没有
        那就使用accept*/
        if(use_accept4)
        {
            s = accept4(oldc->fd, &mysockaddr, &socklen, SOCK_NONBLOCK);
        }
        else
        {
            s = accept(oldc->fd, &mysockaddr, &socklen);
        }
        /*有错误，比如socklen没有初始化*/
        if(s == -1)
        {
            err = errno;
            if(err == EAGAIN)
            {
                return;
            } 
            level = NGX_LOG_ALERT;
            if(err == ECONNRESET)
            {
                level = NGX_LOG_ERR;
            }
            else if (err == EMFILE || err == ENFILE)
            {
                level = NGX_LOG_CRIT;
            }
            /*accept4()函数没实现，返回去使用accept并设置为非阻塞套接字*/
            if(use_accept4 && err == ENOSYS) 
            {
                use_accept4 = 0;  //标记不使用accept4()函数，改用accept()函数
                continue;         //回去重新用accept()函数搞
            }
            /*对方关闭套接字*/
            if(err == ECONNABORTED)
            {
                //...
            }
            return;
        }

        /*控制连接数*/
        if(m_onlineUserCount >= m_worker_connections)
        {
            ngx_log_stderr(0,"超出系统允许的最大连入用户数(最大允许连入数%d)\
                关闭连入请求(%d)。",m_worker_connections,s);
            close(s);
            return;
        }
    
        /*针对大量的连接接入然后又断开的措施*/
        if(m_connectionList.size() > (m_worker_connections * 5))
        {
            if(m_freeconnectionList.size() < m_worker_connections)
            {
                close(s);
                return;
            }
        }

        /*新连入用户连接，分配连接*/
        newc = ngx_get_connection(s);
        if(newc == NULL)
        {
            if(close(s) == -1)
            {
                ngx_log_error_core(NGX_LOG_ALERT,errno,
                    "CSocekt::ngx_event_accept()中close(%d)失败!",s);
            }
            return;
        }

        /*把新接入的客户端的IP赋值到新分配的连接池当中的s_sockaddr变量中来*/
        memcpy(&newc->s_sockaddr,&mysockaddr,socklen);

        if(!use_accept4)
        {
            if(setnonblocking(s) == false)
            {
                ngx_close_connection(newc);
                return;
            }
        }

        /*连接对象和监听对象关联方便通过连接对象找监听对象关联到监听端口*/
        newc->listening = oldc->listening;
        /*设置数据来时的读处理函数*/
        newc->rhandler = &CSocekt::ngx_read_request_handler;
        /*设置数据发送时的写处理函数*/
        newc->whandler = &CSocekt::ngx_write_request_handler;
        /*新用户加到红黑树当中来*/
        if(ngx_epoll_oper_event(s,                  //socekt句柄
                                EPOLL_CTL_ADD,      //事件类型，这里是增加
                                EPOLLIN|EPOLLRDHUP, //标志，这里代表要增加的标志,EPOLLIN：可读，EPOLLRDHUP：TCP连接的远端关闭或者半关闭 ，如果边缘触发模式可以增加 EPOLLET
                                0,                  //对于事件类型为增加的，不需要这个参数
                                newc                //连接池中的连接
                                ) == -1)
        {
            ngx_close_connection(newc);
            return;
        }

        /*针对心跳包增加的时间队列*/
        if(m_ifkickTimeCount == 1)
        {
            AddToTimerQueue(newc);
        }
        /*连入用户数量+1*/
        ++m_onlineUserCount;
        break;
    }while(1);
    return;
}
